use cgmath::vec2;

use crate::{
    input::{Input, InputMap},
    math::Rotation,
    random::Random,
    tetris, Board, Tetris,
};

pub enum Ret {
    Continue(Event),
    Exit,
}

pub enum Event {
    None,
    Updated,
}

pub struct Game<'res> {
    models: &'res Vec<tetris::Model>,
    random: Random,
    tick: usize,
    board: Board,
    tetris: Tetris<'res>,
    next: usize,
}

impl<'r> Game<'r> {
    pub fn new(models: &'r Vec<tetris::Model>, size: (usize, usize)) -> Self {
        let mut random = Random::new(3);
        let cur = random.gen_range(0..models.len());
        Self {
            models,
            next: random.gen_range(0..models.len()),
            board: Board::new(size.0, size.1),
            tetris: Tetris::new(vec2(10, size.1 as _), &models[cur]),
            random,
            tick: 0,
        }
    }

    pub fn tick(&mut self, inputs: &mut Input<InputMap>, delta_millis: usize) -> Ret {
        let (newtick, _) = self.tick.overflowing_add(1);
        self.tick = newtick;

        let mut fall = false;
        let mut movement = vec2(0, 0);
        let mut rotation = Rotation::Idle;

        while let Some(input) = inputs.poll() {
            match input {
                InputMap::MoveLeft => movement.x -= 1,
                InputMap::MoveRight => movement.x += 1,
                InputMap::Enter => todo!(),
                InputMap::MoveDown => fall = true,
                InputMap::Rotate => rotation.rotate_ccw(),
                InputMap::Exit => return Ret::Exit,
            }
        }

        // process user input
        let mut collide_bounds = false;
        for block in self.tetris.after_move_blocks(movement) {
            if block.x < 0
                || block.x >= self.board.width() as i32
                || block.y <= 0
                || self.board.has_block(block.x, block.y)
            {
                collide_bounds = true;
                break;
            }
        }
        if !collide_bounds {
            self.tetris.apply_movement(movement);
        }

        collide_bounds = false;
        for block in self.tetris.after_rotate_blocks(rotation) {
            if block.x < 0
                || block.x >= self.board.width() as i32
                || block.y <= 0
                || self.board.has_block(block.x, block.y)
            {
                collide_bounds = true;
                break;
            }
        }
        if !collide_bounds {
            self.tetris.apply_rotation(rotation)
        }

        // falling
        if self.tick % (10_00 / delta_millis) == 0 || fall {
            let mut collide = false;
            for block in self.tetris.after_move_blocks(vec2(0, -1)) {
                if block.y < 0 || self.board.has_block(block.x, block.y) {
                    collide = true;
                }
            }

            if !collide {
                self.tetris.apply_movement(vec2(0, -1))
            } else {
                let mut need_break = false;
                self.tetris.visit(|x, y, v| {
                    if !self.board.set(x, y, v) {
                        need_break = true;
                    }
                    true
                });

                if need_break {
                    return Ret::Exit;
                }

                let lines = self.board.check_full_lines();
                if !lines.is_empty() {
                    self.board.clear(lines);
                }

                self.tetris.reset(
                    vec2(self.board.width() as i32 / 2, self.board.height() as _),
                    &self.models[self.next],
                );
                self.next = self.random.gen_range(0..self.models.len());

                return Ret::Continue(Event::Updated);
            }
        }

        Ret::Continue(Event::None)
    }

    pub fn next_model(&self) -> &tetris::Model {
        &self.models[self.next]
    }

    pub fn tetris(&self) -> &Tetris {
        &self.tetris
    }

    pub fn board(&self) -> &Board {
        &self.board
    }
}
